package org.springframework.roo.addon.graph.visual;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import org.springframework.roo.addon.graph.GraphMetadata;
import org.springframework.roo.model.JavaType;

public class Visualizer
{
    interface LineFilter
    {
        boolean excludeLine( String line );
    }

    private static final LineFilter INCLUDE_ALL = new LineFilter()
    {
        public boolean excludeLine( String line )
        {
            return false;
        }
    };
    private static final LineFilter EXCLUDE_HEADER = new LineFilter()
    {
        public boolean excludeLine( String line )
        {
            return line.startsWith( "package " ) || line.startsWith( "import" )
                   || line.isEmpty();
        }
    };

    private final PrintStream out;
    private final Queue<GraphMetadata> workQueue = new LinkedList<GraphMetadata>();
    private final Set<JavaType> seen = new HashSet<JavaType>();
    private final boolean includeSource;
    private final LineFilter filter;

    private Visualizer( String outputFileName, boolean includeSource,
            LineFilter filter ) throws FileNotFoundException
    {
        this.out = new PrintStream( outputFileName );
        this.includeSource = includeSource;
        this.filter = filter == null ? INCLUDE_ALL : filter;
    }

    private void printlines( String... lines )
    {
        for ( String line : lines )
        {
            out.println( line );
        }
    }

    public static void generateGraph( GraphMetadata metadata, String itdFilename )
    {
        final Visualizer visualizer;
        try
        {
            visualizer = new Visualizer( itdFilename + ".gv", true,
                    EXCLUDE_HEADER );
        }
        catch ( FileNotFoundException e )
        {
            return;
        }
        try
        {
            visualizer.start( metadata.getJavaType() );
            visualizer.generalStyle();
            visualizer.generateAll( metadata );
        }
        finally
        {
            visualizer.done();
        }
    }

    private void generateAll( GraphMetadata metadata )
    {
        add( metadata );
        final Set<String> edges = new HashSet<String>();
        while ( !workQueue.isEmpty() )
        {
            edges.addAll( generateFrom( workQueue.poll() ) );
        }
        for ( String edge : edges )
        {
            out.println( edge );
        }
    }

    private void add( GraphMetadata metadata )
    {
        if ( seen.add( metadata.getJavaType() ) )
        {
            workQueue.add( metadata );
        }
    }

    private Collection<String> generateFrom( GraphMetadata metadata )
    {
        node( metadata.getJavaType().getSimpleTypeName(),
                metadata.isRelationship() ? "    style=dotted" : "",
                "label = \"{" + sourceCode( metadata ) + properties( metadata )
                        + "}\"" );
        List<String> edges = new LinkedList<String>();
//        for ( RelatedEntry related : metadata.relatedEntities() )
//        {
//            GraphMetadata source = related.getSource(), target = related.getTarget();
//            edges.add( source.getJavaType().getSimpleTypeName() + " -> "
//                       + target.getJavaType().getSimpleTypeName() );
//            add( source );
//            add( target );
//        }
        return edges;
    }

    private void node( String name, String... lines )
    {
        out.println( "  " + name + " [" );
        for ( String line : lines )
        {
            out.println( "    " + line );
        }
        out.println( "  ]" );
    }

    private String sourceCode( GraphMetadata metadata )
    {
        File sourceFile = includeSource ? metadata.getSourceFile() : null;
        if ( sourceFile == null )
        {
            return "class " + metadata.getJavaType().getSimpleTypeName()
                   + " \\{ ... \\}";
        }
        StringBuilder result = new StringBuilder();
        for ( String line : readLines( sourceFile ) )
        {
            if ( filter.excludeLine( line ) ) continue;
            if ( line.startsWith( " " ) || line.startsWith( "\t" ) )
            {
                result.append( "\\" );
            }
            result.append( escape( line, "\\", "\"", "{", "}", "<", ">", "|" ) );
            result.append( "\\\n    \\l" );
        }
        return result.toString();
    }

    private String properties( GraphMetadata metadata )
    {
        StringBuilder result = new StringBuilder();
//        for ( PropertyEntry property : metadata.properties() )
//        {
//            result.append( "|\\l" );
//            result.append( property.getType() );
//            result.append( " " );
//            result.append( property.getName() );
//        }
        return result.toString();
    }

    private static String escape( String string, String... chars )
    {
        for ( String chr : chars )
        {
            string = string.replace( chr, "\\" + chr );
        }
        return string;
    }

    private void start( JavaType javaType )
    {
        printlines( "digraph " + javaType.getSimpleTypeName() + " {" );
    }

    private void generalStyle()
    {
        printlines( "  node [", "    fontname = \"Bitstream Vera Sans\"",
                "    shape = \"Mrecord\"", "  ]" );
        printlines( "  edge [", "    fontname = \"Bitstream Vera Sans\"", "  ]" );
        printlines( "" );
    }

    private void done()
    {
        printlines( "\n}" );
        out.close();
    }

    private static Iterable<String> readLines( File file )
    {
        final BufferedReader reader;
        final List<String> lines = new LinkedList<String>();
        try
        {
            reader = new BufferedReader( new FileReader( file ) );
        }
        catch ( FileNotFoundException e )
        {
            return lines;
        }
        try
        {
            String line;
            while ( ( line = reader.readLine() ) != null )
            {
                int len = line.length();
                while ( len > 0
                        && Character.isWhitespace( line.charAt( len - 1 ) ) )
                {
                    len--;
                }
                lines.add( line.substring( 0, len ) );
            }
            return lines;
        }
        catch ( IOException e )
        {
            return lines;
        }
    }
}
